package com.learn.utils;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.UUID;
import com.learn.config.VideoConfig;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.exception.CosServiceException;
import com.qcloud.cos.http.HttpProtocol;
import com.qcloud.cos.model.ObjectMetadata;
import com.qcloud.cos.model.PutObjectRequest;
import com.qcloud.cos.model.StorageClass;
import com.qcloud.cos.model.UploadResult;
import com.qcloud.cos.region.Region;
import com.qcloud.cos.transfer.TransferManager;
import com.qcloud.cos.transfer.TransferManagerConfiguration;
import com.qcloud.cos.transfer.Upload;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacpp.Loader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/** @Author huyi @Date 2021/10/20 12:11 @Description: mp4提取音频工具类 */
@Component
@Slf4j
public class Mp4ExtractAudioUtils {

  @Autowired
  private VideoConfig videoConfig;
 
  /**
   * 获取视频的文件pcm文件地址
   *
   * @param url MP4
   * @return
   * @throws Exception
   */
  public  String getMp4Pcm(String url, String tmpDir) throws Exception {
    Optional<String> pcmPath = Optional.empty();
    try {
      pcmPath = convertMP4toPCM(url, Paths.get(tmpDir));
    } catch (Exception exception) {
      exception.printStackTrace();
      throw new Exception("转换pcm异常:" + exception.getMessage());
    }
    if (pcmPath.isPresent()) {
      return pcmPath.get();
    } else {
      throw new Exception("视频转换音频失败");
    }
  }
  /**
   * 将单个PM4文件进行片头和片尾歌曲删除后，转换为PCM文件
   *
   * @param mp4Path
   * @param pcmDir
   * @return 转换完成后的pcm文件路径
   */
  public  Optional<String> convertMP4toPCM(String mp4Path, Path pcmDir) {
    String ffmpeg = Loader.load(org.bytedeco.ffmpeg.ffmpeg.class);
    // 基于ffmpeg进行pcm转换
    // 基于输入路径的md5值来命名，也可以基于系统时间戳来命名
    String pcmFile = pcmDir.resolve(UUID.randomUUID() + ".pcm").toString();
    ProcessBuilder pcmBuilder =
        new ProcessBuilder(
            ffmpeg,
            "-y",
            "-i",
//            mp4Path.toAbsolutePath().toString(),
                mp4Path,
            "-vn",
            "-acodec",
            "pcm_s16le",
            "-f",
            "s16le",
            "-ac",
            "1",
            "-ar",
            "16000",
            pcmFile);
    try {
      // inheritIO是指将 子流程的IO与当前java流程的IO设置为相同
      pcmBuilder.inheritIO().start().waitFor();
    } catch (InterruptedException | IOException e) {
      log.error("ffmpeg将mp4转换为pcm时出错", e);
      return Optional.empty();
    }
    // 返回pcm文件路径
    return Optional.of(pcmFile);
  }
 
  /**
   * 根据PCM文件构建wav的header字段
   *
   * @param srate Sample rate - 8000, 16000, etc.
   * @param channel Number of channels - Mono = 1, Stereo = 2, etc..
   * @param format Number of bits per sample (16 here)
   * @throws IOException
   */
  public  byte[] buildWavHeader(int dataLength, int srate, int channel, int format)
      throws IOException {
    byte[] header = new byte[44];
 
    long totalDataLen = dataLength + 36;
    long bitrate = srate * channel * format;
 
    header[0] = 'R';
    header[1] = 'I';
    header[2] = 'F';
    header[3] = 'F';
    header[4] = (byte) (totalDataLen & 0xff);
    header[5] = (byte) ((totalDataLen >> 8) & 0xff);
    header[6] = (byte) ((totalDataLen >> 16) & 0xff);
    header[7] = (byte) ((totalDataLen >> 24) & 0xff);
    header[8] = 'W';
    header[9] = 'A';
    header[10] = 'V';
    header[11] = 'E';
    header[12] = 'f';
    header[13] = 'm';
    header[14] = 't';
    header[15] = ' ';
    header[16] = (byte) format;
    header[17] = 0;
    header[18] = 0;
    header[19] = 0;
    header[20] = 1;
    header[21] = 0;
    header[22] = (byte) channel;
    header[23] = 0;
    header[24] = (byte) (srate & 0xff);
    header[25] = (byte) ((srate >> 8) & 0xff);
    header[26] = (byte) ((srate >> 16) & 0xff);
    header[27] = (byte) ((srate >> 24) & 0xff);
    header[28] = (byte) ((bitrate / 8) & 0xff);
    header[29] = (byte) (((bitrate / 8) >> 8) & 0xff);
    header[30] = (byte) (((bitrate / 8) >> 16) & 0xff);
    header[31] = (byte) (((bitrate / 8) >> 24) & 0xff);
    header[32] = (byte) ((channel * format) / 8);
    header[33] = 0;
    header[34] = 16;
    header[35] = 0;
    header[36] = 'd';
    header[37] = 'a';
    header[38] = 't';
    header[39] = 'a';
    header[40] = (byte) (dataLength & 0xff);
    header[41] = (byte) ((dataLength >> 8) & 0xff);
    header[42] = (byte) ((dataLength >> 16) & 0xff);
    header[43] = (byte) ((dataLength >> 24) & 0xff);
 
    return header;
  }
 
  /**
   * 默认写入的pcm数据是16000采样率，16bit，可以按照需要修改
   *
   * @param filePath
   * @param pcmData
   */
  public  String writeToFile(String filePath, byte[] pcmData) {
    BufferedOutputStream bos = null;
    try {
      bos = new BufferedOutputStream(new FileOutputStream(filePath));
      byte[] header = buildWavHeader(pcmData.length, 16000, 1, 16);
      bos.write(header, 0, 44);
      bos.write(pcmData);
      bos.close();
      return filePath;
    } catch (Exception e) {
      e.printStackTrace();
      return filePath;
    } finally {
      if (bos != null) {
        try {
          bos.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }
 
  /**
   * 提取音频
   * @param mp4Path MP4地址
   * @param tmpDir 临时文件夹
   * @return 音频地址
   * @throws Exception 异常
   */
  public  String extractAudio(String mp4Path, String tmpDir) {
    try {
      String pcmPath = getMp4Pcm(mp4Path, tmpDir);
     return upYun(FileUtil.readBytes(pcmPath));
    }catch (Exception e){
      System.out.println(e.getMessage());
    }
    return null;
//    return writeToFile(resultPath, FileUtil.readBytes(pcmPath));
  }

  /**
   * 上传音频文件到对象存储
   * * * */
  public  String upYun(byte[] pcmPath) throws IOException {
    // 使用高级接口必须先保证本进程存在一个 TransferManager 实例，如果没有则创建
// 详细代码参见本页：高级接口 -> 创建 TransferManager
    TransferManager transferManager = createTransferManager();

// 存储桶的命名格式为 BucketName-APPID，此处填写的存储桶名称必须为此格式
    String bucketName = videoConfig.getBucketName();
// 对象键(Key)是对象在存储桶中的唯一标识。
    SecureRandom secureRandom = new SecureRandom();
    String key =String.valueOf(secureRandom.nextLong()) ;

// 这里创建一个 ByteArrayInputStream 来作为示例，实际中这里应该是您要上传的 InputStream 类型的流
//    long inputStreamLength = 1024 * 1024;
//    byte data[] = new byte[inputStreamLength];
    InputStream pcmStream = new ByteArrayInputStream(pcmPath);
    byte[] header = buildWavHeader(pcmPath.length, 16000, 1, 16);
    ByteArrayInputStream heStream = new ByteArrayInputStream(header);
    SequenceInputStream inputStream = new SequenceInputStream(heStream, pcmStream);


    ObjectMetadata objectMetadata = new ObjectMetadata();
// 上传的流如果能够获取准确的流长度，则推荐一定填写 content-length
// 如果确实没办法获取到，则下面这行可以省略，但同时高级接口也没办法使用分块上传了
//    objectMetadata.setContentLength(inputStreamLength);

    PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, inputStream, objectMetadata);

// 设置存储类型（如有需要，不需要请忽略此行代码）, 默认是标准(Standard), 低频(standard_ia)
// 更多存储类型请参见 https://cloud.tencent.com/document/product/436/33417
    putObjectRequest.setStorageClass(StorageClass.Standard);

    try {
      // 高级接口会返回一个异步结果Upload
      // 可同步地调用 waitForUploadResult 方法等待上传完成，成功返回 UploadResult, 失败抛出异常
      Upload upload = transferManager.upload(putObjectRequest);
      UploadResult uploadResult = upload.waitForUploadResult();
      System.out.println(uploadResult.getKey());
      return uploadResult.getKey();
    } catch (CosServiceException e) {
      e.printStackTrace();
    } catch (CosClientException e) {
      e.printStackTrace();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

// 确定本进程不再使用 transferManager 实例之后，关闭即可
// 详细代码参见本页：高级接口 -> 关闭 TransferManager
    shutdownTransferManager(transferManager);

    return null;

  }

  // 创建 TransferManager 实例，这个实例用来后续调用高级接口
  public  TransferManager createTransferManager() {
    // 创建一个 COSClient 实例，这是访问 COS 服务的基础实例。
    // 详细代码参见本页: 简单操作 -> 创建 COSClient
    COSClient cosClient = createCOSClient();

    // 自定义线程池大小，建议在客户端与 COS 网络充足（例如使用腾讯云的 CVM，同地域上传 COS）的情况下，设置成16或32即可，可较充分的利用网络资源
    // 对于使用公网传输且网络带宽质量不高的情况，建议减小该值，避免因网速过慢，造成请求超时。
    ExecutorService threadPool = Executors.newFixedThreadPool(32);

    // 传入一个 threadpool, 若不传入线程池，默认 TransferManager 中会生成一个单线程的线程池。
    TransferManager transferManager = new TransferManager(cosClient, threadPool);

    // 设置高级接口的配置项
    // 分块上传阈值和分块大小分别为 5MB 和 1MB
    TransferManagerConfiguration transferManagerConfiguration = new TransferManagerConfiguration();
    transferManagerConfiguration.setMultipartUploadThreshold(5*1024*1024);
    transferManagerConfiguration.setMinimumUploadPartSize(1*1024*1024);
    transferManager.setConfiguration(transferManagerConfiguration);

    return transferManager;
  }

  // 创建 COSClient 实例，这个实例用来后续调用请求
  public  COSClient createCOSClient() {
    // 设置用户身份信息。
    // SECRETID 和 SECRETKEY 请登录访问管理控制台 https://console.cloud.tencent.com/cam/capi 进行查看和管理

    COSCredentials cred = new BasicCOSCredentials(videoConfig.getSecretId(), videoConfig.getSecretKey());

    // ClientConfig 中包含了后续请求 COS 的客户端设置：
    ClientConfig clientConfig = new ClientConfig();

    // 设置 bucket 的地域
    // COS_REGION 请参见 https://cloud.tencent.com/document/product/436/6224
    clientConfig.setRegion(new Region("ap-guangzhou"));

    // 设置请求协议, http 或者 https
    // 5.6.53 及更低的版本，建议设置使用 https 协议
    // 5.6.54 及更高版本，默认使用了 https
    clientConfig.setHttpProtocol(HttpProtocol.https);

    // 以下的设置，是可选的：

    // 设置 socket 读取超时，默认 30s
    clientConfig.setSocketTimeout(30*1000);
    // 设置建立连接超时，默认 30s
    clientConfig.setConnectionTimeout(30*1000);

    // 如果需要的话，设置 http 代理，ip 以及 port
//    clientConfig.setHttpProxyIp("httpProxyIp");
//    clientConfig.setHttpProxyPort(80);

    // 生成 cos 客户端。
    return new COSClient(cred, clientConfig);
  }

   void shutdownTransferManager(TransferManager transferManager) {
    // 指定参数为 true, 则同时会关闭 transferManager 内部的 COSClient 实例。
    // 指定参数为 false, 则不会关闭 transferManager 内部的 COSClient 实例。
    transferManager.shutdownNow(true);
  }

}